﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using AForge.Video;
using LprWrapperSharp;
using MiscUtil;
using OpenCvSharp;
using ImageFormat = LprWrapperSharp.ImageFormat;

namespace RemoteImaging.LicensePlate
{
    class LprSearcher : ILicenseplateProvider
    {
        private readonly IVideoSource _videoSource;
        private readonly IAppState _appState;
        private readonly ILicensePlateEventPublisher _eventPublisher;
        private RecognizeResult _lastLprNumber = new RecognizeResult() { PlateNumber = string.Empty };
        private LprSender _lprSender;

        private readonly ConcurrentQueue<StampedFrame> _framesQueue
            = new ConcurrentQueue<StampedFrame>();

        private int QueueCapacity { get; set; }

        private LprWrapperSharp.LprRecognizer _recognizer;
        private System.Threading.Thread _worker;

        private DateTime _lastFrameTime;
        private LicensePlate.LicensePlateCheckService _checkService;


        private Point _reverseDirection = Point.Empty;
        private readonly object _reverseDirectionLock = new object();
        public Point ReverseDirection
        {
            get
            {
                lock (_reverseDirectionLock)
                {
                    return _reverseDirection;
                }
            }
            set
            {
                lock (_reverseDirectionLock)
                {
                    _reverseDirection = value;
                }
            }
        }

        private Rectangle _roi;
        private object _roiLock = new object();
        public Rectangle Roi
        {
            get
            {
                lock (_roiLock)
                {
                    return _roi;
                }
            }
            set
            {
                lock (_roiLock)
                {
                    _roi = value;
                }
            }
        }



        public int CalcImageBufferPercent()
        {
            return (int)(_framesQueue.Count / (float)QueueCapacity * 100);
        }

        public TimeSpan FrameInterval { get; set; }

        public LprSearcher(
            IVideoSource videoSource,
            IAppState appState,
            ILicensePlateEventPublisher eventPublisher)
        {
            if (videoSource == null) throw new ArgumentNullException("videoSource");
            if (appState == null) throw new ArgumentNullException("appState");
            if (eventPublisher == null) throw new ArgumentNullException("eventPublisher");

            FrameInterval = TimeSpan.FromMilliseconds(Properties.Settings.Default.LprFrameIntervalInMs);
            Properties.Settings.Default.PropertyChanged += (s, e) => FrameInterval = TimeSpan.FromMilliseconds(Properties.Settings.Default.LprFrameIntervalInMs);

            QueueCapacity = 10;

            _videoSource = videoSource;
            _appState = appState;
            _eventPublisher = eventPublisher;

            _checkService = new LicensePlateCheckService();

            _videoSource.NewFrame += (s, e) =>
                                         {
                                             if (!Properties.Settings.Default.EnableLprRecognize)
                                             {
                                                 return;
                                             }

                                             if (!_appState.IsExiting)
                                             {
                                                 var now = DateTime.Now;
                                                 //System.Diagnostics.Debug.WriteLine("new frame arrived at " + now.Millisecond);
                                                 if (_framesQueue.Count <= QueueCapacity && (now - _lastFrameTime) >= FrameInterval)
                                                 {
                                                     var bmp = AForge.Imaging.Image.Clone(e.Frame);

                                                     _framesQueue.Enqueue(new StampedFrame(bmp));
                                                     RaiseImageBufferChanged();
                                                     _lastFrameTime = now;
                                                     //System.Diagnostics.Debug.WriteLine("lpr frame: " + now.Millisecond);
                                                 }



                                             }
                                         };

            RecognizeParameters = new RecognizeParameters();

        }

        private void RaiseImageBufferChanged()
        {
            var percent = CalcImageBufferPercent();
            Messenger.Instance.RaiseLprImageBufferChanged(new EventArgs<int>(percent));
        }

        public void Start()
        {
            if (_worker == null)
            {
                _recognizer = new LprRecognizer();
                _recognizer.SetImageFormat(ImageFormat.BGR, false, true);
                _recognizer.SetCharacterPriority("川");


                if (string.IsNullOrEmpty(Properties.Settings.Default.UploadServer))
                {
                    MessageBox.Show("没有正确配置上传服务器IP，图片无法上传，请重新配置上传服务器IP");
                }
                else
                {
                    _lprSender = LprSender.Instance(
                        Properties.Settings.Default.UploadServer,
                        Properties.Settings.Default.UploadServerPort);
                }



                _worker = new Thread(WorkerFunc);
                _worker.IsBackground = true;
                _worker.Start();

            }
        }

        private void WorkerFunc()
        {
            var stopWatch = new System.Diagnostics.Stopwatch();
            while (!_appState.IsExiting)
            {
                StampedFrame frame = null;
                if (_framesQueue.TryDequeue(out frame))
                {
                    stopWatch.Restart();
                    RaiseImageBufferChanged();
                    RecognizeFrame(frame);
                    System.Diagnostics.Debug.WriteLine("millisecond used to process frame: " + stopWatch.ElapsedMilliseconds);
                }
                else
                {
                    Thread.Sleep(1000);
                }
            }

            ClearQueue();
        }

        public RecognizeResult RecogImage(System.Drawing.Bitmap bmp)
        {
            var clone = AForge.Imaging.Image.Clone((Bitmap)bmp);
            var rc = new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height);
            var rr = _recognizer.RecogImage(clone, rc.Width, rc.Height);
            clone.Dispose();
            return rr;
        }

        private void RecognizeFrame(StampedFrame frame)
        {
            var croppedImage = CropImage(frame.Bmp);
            var result = RecognizeOneLpr(croppedImage);
            if (result.Succeed && IsValidLpr(result))
            {
                var lpi = new LicensePlateSpec();
                lpi.Image = AForge.Imaging.Image.Clone(frame.Bmp);
                lpi.LicensePlateNumber = result.PlateNumber;
                lpi.CapturedFrom = 0;
                lpi.LicensePlateColor = result.PlateColor;
                lpi.CaptureTime = frame.TimeStamp;

                if (!IsSimilar(result.PlateNumber))
                {
                    var clone = lpi.Clone();
                    _checkService.Check(lpi.LicensePlateNumber, r =>
                    {
                        if (_lprSender != null)
                        {
                            if (r != null)
                            {
                                _lprSender.Enqueue(clone);
                            }
                            else if (!Properties.Settings.Default.OnlyUploadSuspectCar)
                            {
                                _lprSender.Enqueue(clone);
                            }
                        }

                    });

                    InvokeNewLicenseplate(new EventArgs<LicensePlateSpec>(lpi));

                }
                else if (IsDrivingReversely(result))
                {
                    lpi.isDrivingReversly = true;
                    InvokeNewLicenseplate(new EventArgs<LicensePlateSpec>(lpi));
                }


                _lastLprNumber = result;
            }
            frame.Bmp.Dispose();
        }

        private bool IsValidLpr(RecognizeResult plateNumber)
        {
            if (!plateNumber.Succeed) return false;

            var numberPart = plateNumber.PlateNumber.Substring(plateNumber.PlateNumber.Length - 5, 5);
            var numberCount = numberPart.Count(c => char.IsNumber(c));
            return numberCount >= 3;
        }


        //        0 //未知
        //1 //蓝
        //2 //黄
        //3 //白
        //4 //黑
        //5 //绿


        private bool IsSimilar(string lpr)
        {
            var count = 0;
            var lastPlate = _lastLprNumber.PlateNumber;
            var len = Math.Min(lastPlate.Length, lpr.Length);
            for (int i = 0; i < len; i++)
            {
                if (lastPlate[i] == lpr[i])
                {
                    ++count;
                }
            }

            return count >= 5;
        }

        private void ClearQueue()
        {
            //dispose remaining elements
            while (!_framesQueue.IsEmpty)
            {
                StampedFrame frame = null;
                if (_framesQueue.TryDequeue(out frame))
                {
                    frame.Bmp.Dispose();
                }
            }
        }

        public event EventHandler<EventArgs<LicensePlateSpec>> NewLicenseplate;

        public void InvokeNewLicenseplate(EventArgs<LicensePlateSpec> e)
        {
            EventHandler<EventArgs<LicensePlateSpec>> handler = NewLicenseplate;
            if (handler != null) handler(this, e);
        }

        public RecognizeParameters RecognizeParameters { get; set; }


        private bool _enableSchema;
        private object _enableSchemaLock = new object();
        public bool EnableSchema
        {
            get
            {
                lock (_enableSchemaLock)
                {
                    return _enableSchema;
                }
            }
            set
            {
                lock (_enableSchemaLock)
                {
                    _enableSchema = value;
                }
            }
        }

        private object _specLock = new object();
        private List<ProjectSpecification> _projectSpecifications = new List<ProjectSpecification>();
        public List<ProjectSpecification> ProjectSpecifications
        {
            get
            {
                lock (_specLock)
                {
                    return _projectSpecifications;
                }
            }
            set
            {
                lock (_specLock)
                {
                    _projectSpecifications = value;
                }
            }
        }


        private RecognizeResult RecognizeOneLpr(Bitmap imageToReco)
        {
            var results = new List<RecognizeResult>();
            RecognizeResult orgRr;
            if (Math.Abs(RecognizeParameters.ZoomFactor - 1.0f) > 0.05)
            {
                var resizer = new AForge.Imaging.Filters.ResizeBicubic((int)(imageToReco.Width * RecognizeParameters.ZoomFactor),
                                                                       (int)(imageToReco.Height * RecognizeParameters.ZoomFactor));
                var resized = resizer.Apply(imageToReco);
                orgRr = RecogImage(resized);
            }
            else
                orgRr = RecogImage(imageToReco);
            results.Add(orgRr);

            var specs = ProjectSpecifications;

            if (specs != null && EnableSchema)
            {
                foreach (var spec in specs)
                {
                    var org = IplImage.FromBitmap((Bitmap)imageToReco);
                    var resize = Helper.ResizeIpl(org, new CvSize(spec.ResizeTo.Width, spec.ResizeTo.Height));
                    var proj = Helper.Project(resize, spec.ProjectPoints, spec.PlateSize.Width, spec.PlateSize.Height);
                    var img = proj.ToBitmap();
                    var pr = RecogImage(img);

                    results.Add(pr);
                }
            }

            var result = SelectBestResult(results);

            return result;
        }

        private Bitmap CropImage(Bitmap imageToReco)
        {
            if (IsRoiValid(imageToReco))
            {
                var crop = new AForge.Imaging.Filters.Crop(Roi);
                return crop.Apply(imageToReco);
            }
            else
            {
                return imageToReco;
            }
        }

        private bool IsRoiValid(Bitmap imageToReco)
        {
            var notEmpty = Roi != Rectangle.Empty && Roi.Width != 0 && Roi.Height != 0;
            var imgRectangle = new Rectangle(0, 0, imageToReco.Width, imageToReco.Height);
            return notEmpty && imgRectangle.Contains(Roi);
        }

        private bool IsDrivingReversely(RecognizeResult result)
        {
            var refenceDirection = ReverseDirection;
            if (refenceDirection == Point.Empty)
            {
                return false;
            }

            var c2 = GetCenter(result.PlateRectangle);
            var c1 = GetCenter(_lastLprNumber.PlateRectangle);
            var d1 = new Point(c2.X - c1.X, c2.Y - c1.Y);
            if (d1 == Point.Empty)
            {
                return false;
            }

            return IsInSameDirection(refenceDirection, d1);
        }


        private static Point GetCenter(Rectangle rectangle)
        {
            return new Point(rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height / 2);
        }

        private static RecognizeResult SelectBestResult(List<RecognizeResult> results)
        {
            var ordered = from r in results
                          where r.Succeed
                          orderby r.Confidence descending
                          select r;

            var gg = from rr in ordered
                     group rr by rr.PlateNumber
                         into g
                         select g;

            var maxOrdered = from g in gg
                             orderby g.Count() descending
                             select g;

            var max = maxOrdered.FirstOrDefault();

            if (max != null)
            {
                return max.FirstOrDefault();
            }

            return new RecognizeResult();
        }

        private static bool IsInSameDirection(Point p1, Point p2)
        {
            var d = p1.X * p2.X + p1.Y * p2.Y;
            var d2 = Math.Sqrt(p1.X * p1.X + p1.Y * p1.Y) * Math.Sqrt(p2.X * p2.X + p2.Y * p2.Y);
            var cosAlpha = d / d2;

            var cos10 = Math.Cos(15 * Math.PI / 180); //小于30度夹角，判断为相同方向
            return cosAlpha >= cos10;
        }

    }


    internal class StampedFrame
    {
        public Bitmap Bmp { get; set; }
        public DateTime TimeStamp { get; set; }

        public StampedFrame(System.Drawing.Bitmap bmp)
        {
            Bmp = bmp;
            TimeStamp = DateTime.Now;
        }
    }
}
